/*
 *
 *  *    Copyright © OpenAtom Foundation.
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *         http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.inspur.edp.commonmodel.engine.core.data;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.inspur.edp.cef.core.data.EntityDataBase;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.core.data.EntityDataCollection;
import com.inspur.edp.cef.entity.entity.*;
import com.inspur.edp.cef.entity.entity.dynamicProp.DynamicPropSetImpl;
import com.inspur.edp.cef.entity.i18n.MultiLanguageInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.EntityResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.commonmodel.engine.api.data.AssociationInfo;
import com.inspur.edp.commonmodel.engine.api.data.IEngineEntityData;
import com.inspur.edp.commonmodel.engine.core.common.CMUtil;
import com.inspur.edp.commonmodel.engine.core.data.serializer.CommEngineDataSerializer;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.das.commonmodel.IObjectCollection;
import com.inspur.edp.commonmodel.engine.core.exception.CMEngineCoreException;
import com.inspur.edp.commonmodel.engine.core.exception.ErrorCodes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@JsonSerialize(using = CommEngineDataSerializer.class)
public class EngineRootData extends EntityDataBase implements IValuesContainer, Cloneable, IEntityData, IMultiLanguageData, IEngineEntityData {

    @JSONField(serialize = false)
    protected final IGspCommonObject node;
    protected final CefEntityResInfoImpl resInfo;

    private HashMap<String, Object> values;
    private HashMap<String, IEntityDataCollection> childs;

    @Deprecated
    public EngineRootData(IGspCommonObject node) {
        this(node, null);
    }

    public EngineRootData(IGspCommonObject node, CefEntityResInfoImpl resInfo) {
        this.node = node;
        this.resInfo = resInfo;
    }

    @Override
    public HashMap<String, IEntityDataCollection> innerGetChilds() {
        return (HashMap<String, IEntityDataCollection>) getInnerGetChilds().clone();
    }

    protected HashMap<String, IEntityDataCollection> getInnerGetChilds() {
        if (childs == null) {
            IObjectCollection childNodes = node.getContainChildObjects();
            childs = new HashMap<>(childNodes.size(), 1);
            for (IGspCommonObject child : childNodes) {
                EntityResInfo childResInfo = resInfo != null ? resInfo.getChildEntityResInfo(child.getCode()) : null;
                EntityDataCollection collection = childResInfo == null ?
                        new EntityDataCollection() : new EntityDataCollection((CefEntityResInfoImpl) childResInfo);
                childs.put(child.getCode(), collection);
            }
        }
        return childs;
    }

    @Override
    public ICefData innerCreateChild(String s) {
        IGspCommonObject childNode = CMUtil.checkNodeExists(node, s, getNodePredicate());
        EntityResInfo childResInfo = resInfo != null ? resInfo.getChildEntityResInfo(childNode.getCode()) : null;
        return new EngineChildData(childNode, (CefEntityResInfoImpl) childResInfo);
    }

    @Override
    public void innerMergeChildData(String s, ArrayList<IEntityData> childData) {
        IEntityDataCollection childDataCollection = getChilds().get(s);
        if (childDataCollection == null) {
            CMUtil.throwNodeNotFound(node, s);
        }
        for (IEntityData data : childData) {
            childDataCollection.add(data);
        }
    }

    @Override
    public Object innerGetValue(String labelId) {
        IGspCommonElement element = CMUtil
                .checkElementExists(node, labelId, getElementPredicate());
        if (element.getObjectType() == GspElementObjectType.DynamicProp) {
            Object rez = getValues().get(labelId);
            if (rez == null) {
                getValues().put(labelId, (rez = new DynamicPropSetImpl()));
            }
            return rez;
        } else if (!element.getIsRefElement()) {
            return getValues().get(labelId);
        } else {
            String parentLabelId = element.getParentAssociation().getBelongElement().getLabelID();
            Object parentValue = getValues().get(parentLabelId);
            if (parentValue == null) {
                return null;
            } else {
                return ((AssociationInfo) parentValue).getValue(labelId);
            }
        }
    }

    private HashMap<String, Object> getValues() {
        if (values == null) {
            values = new HashMap<>((int) (node.getContainElements().size()/0.75 + 1));
        }
        return values;
    }

    @Override
    protected ICefData innerCopySelf() {
        try {
            EngineRootData result = (EngineRootData) this.clone();
            result.values = CMUtil.cloneForValues(this.values);
            return result;
        } catch (CloneNotSupportedException e) {
            throw new CMEngineCoreException(e);
        }
    }


    @Override
    protected ICefData innerCopy() {
        try {
            EngineRootData result = (EngineRootData) this.clone();
            result.values = CMUtil.cloneForValues(this.values);
            if (childs != null) {
                result.childs = new HashMap<>(childs.size(), 1);
                for (Map.Entry<String, IEntityDataCollection> pair : childs.entrySet()) {
                    EntityDataCollection dataCollection = new EntityDataCollection();
                    for (IEntityData item : pair.getValue()) {
                        dataCollection.add((IEntityData) item.copy());
                    }
                    result.childs.put(pair.getKey(), dataCollection);
                }
            }
            return result;
        } catch (CloneNotSupportedException e) {
            throw new CMEngineCoreException(e);
        }
    }


    @Override
    protected List<String> innerGetPropertyNames() {
        return CMUtil.buildNodePropertyNames(node, getElementPredicate());
    }

    @Override
    protected void innerSetValue(String s, Object o) {
        CMUtil.checkElementValue(node, s, o, getElementPredicate());
        getValues().put(s, o);
    }

    @Override
    public Object innerCreateValue(String propertyName) {
        GspAssociation gspAssociation = null;
        for (IGspCommonField gspCommonField : node.getContainElements()) {
            if (gspCommonField.getLabelID().equals(propertyName) && gspCommonField.getObjectType() == GspElementObjectType.Association) {
                gspAssociation = gspCommonField.getChildAssociations().get(0);
            }
        }
        return new AssociationInfo(gspAssociation);
    }

    @Override
    public String getID() {
        Object rez = getValues().get(node.getIDElement().getLabelID());
        if (rez == null)
            return null;
        else if (rez instanceof String)
            return (String) rez;
        else if (rez instanceof AssociationInfo) {
            return ((AssociationInfo)rez).getValue();
        }
        else {
            throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1019,
                    node.getBelongModel().getID(), node.getBelongModel().getCode(),
                    node.getBelongModel().getName(), node.getCode(),
                    String.valueOf(node.getIDElement().getObjectType()), rez.getClass().getName());
        }
    }

    @Override
    public void setID(String s) {
        getValues().put(node.getIDElement().getLabelID(), s);
    }

    //#region virtual
    protected Predicate<IGspCommonField> getElementPredicate() {
        return null;
    }

    protected Predicate<IGspCommonObject> getNodePredicate() {
        return null;
    }

    private Map<String, MultiLanguageInfo> multiLanguageInfos;

    @JsonIgnore
    @Deprecated
    public Map<String, MultiLanguageInfo> getMultiLanguageInfos() {
        if (this.multiLanguageInfos == null) {
            this.multiLanguageInfos = new HashMap<>();
        }
        return multiLanguageInfos;
    }

    @Override
    @JSONField(serialize = false)
    public final IGspCommonObject getNode() {
        return node;
    }
    //#endregion
}
